from time import time
from typing import Callable
from fastapi import FastAPI
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from loguru import logger

from core.database import getDB
from core.scheduler import scheduler
from core.device_monitor import DeviceModelMap, createDeviceModel, removeDeviceModel
from core.client_creator import ClientMap, createMqttClient
from core.log import logger
from models.device import DeviceTable
from models.script import ScriptTable
from models.client import MqttClientTable
from schemas.device import PointSchema
from schemas.script import ScriptRunSchema, ScriptSchema
from utils.func import str2func
from utils.network import Gateway, startWatchDog


def initClient():
    logger.info("开始初始化客户端信息...")
    _start = time()
    with getDB() as db:
        clients = db.scalars(select(MqttClientTable)).fetchall()
        for client in clients:
            createMqttClient(
                name=client.name,
                clientId=client.clientId,
                host=client.host,
                port=client.port,
                username=client.username,
                password=client.password,
                willTopic=client.willTopic,
                willPayload=client.willPayload,
                willQoS=client.willQoS,
                willRetain=client.willRetain,
            )
    logger.info(f"客户端初始化完毕...（耗时 {(time() - _start):.2f} s）")


def runTimerScriptArgsDevice(deviceNos: list[str], clientNames: list[str], func: Callable):
    devices = [DeviceModelMap.get(no) for no in deviceNos]
    clients = [ClientMap.get(name) for name in clientNames]
    for dev in devices:
        if not dev:
            continue
        for cli in clients:
            if not cli:
                continue
            try:
                res = func(dev.model)
                if isinstance(res, dict) and res.get("topic") and res.get("payload"):
                    cli.send(**res)
            except Exception as e:
                dev.addLog(e)

def runTimerScriptArgsGateway(clientNames: list[str], func: Callable):
    clients = [ClientMap.get(name) for name in clientNames]
    for cli in clients:
        if not cli:
            continue
        res = func(Gateway())
        if isinstance(res, dict) and res.get("topic") and res.get("payload"):
            cli.send(**res)


def initDeviceMonitor():
    logger.info("开始加载设备模型...")
    _start = time()
    intervalScript: dict[int, ScriptSchema] = {}
    advancedScript: dict[int, ScriptSchema] = {}
    with getDB() as db:
        devs = db.scalars(select(DeviceTable).options(
                selectinload(DeviceTable.netInterface),
                selectinload(DeviceTable.points),
                selectinload(DeviceTable.scripts).options(
                    selectinload(ScriptTable.mqttClients)
                )
            ).where(DeviceTable.disabled != True)).fetchall()
        for dev in devs:
            pollingScript = []
            for s in dev.scripts:
                sDict = s.dict(exclude=["devRelation", "mqttRelation", "devices", "mqttClients"])
                if s.mode == "polling":
                    pollingScript.append(s)
                elif s.mode == "interval":
                    if not intervalScript.get(s.id):
                        intervalScript[s.id] = ScriptSchema(
                            **sDict, 
                            mqttClients=[cli.name for cli in s.mqttClients]
                        )
                    intervalScript[s.id].devices.append(dev.no)
                elif s.mode == "advanced":
                    if not advancedScript.get(s.id):
                        advancedScript[s.id] = ScriptSchema(
                            **sDict, 
                            mqttClients=[cli.name for cli in s.mqttClients]
                        )
                    advancedScript[s.id].devices.append(dev.no)                
            # 创建设备模型采集数据，并初始化轮询类型脚本
            createDeviceModel(
                no=dev.no, name=dev.name, protocol=dev.communicationId, 
                host=dev.netInterface.host, port=dev.netInterface.port,
                srcPoints=[PointSchema(**p.dict(exclude={"deviceNo"})) for p in dev.points],
                srcScripts=[ScriptRunSchema(
                    content=s.content, 
                    clients=[cli.name for cli in s.mqttClients]
                ) for s in pollingScript]
            )
        # 筛选仅读取网关公共信息的脚本
        _scripts = db.scalars(select(ScriptTable).where(ScriptTable.args == 2)).fetchall()
        for s in _scripts:
            sDict = s.dict(exclude=["devRelation", "mqttRelation", "devices", "mqttClients"])
            if s.mode == "interval":
                if not intervalScript.get(s.id):
                    intervalScript[s.id] = ScriptSchema(
                        **sDict, 
                        mqttClients=[cli.name for cli in s.mqttClients]
                    )
            elif s.mode == "advanced":
                if not advancedScript.get(s.id):
                    advancedScript[s.id] = ScriptSchema(
                        **sDict, 
                        mqttClients=[cli.name for cli in s.mqttClients]
                    )
    funcList = [None, runTimerScriptArgsDevice, runTimerScriptArgsGateway]
    # 注入间隔触发任务
    for i in intervalScript:
        _s = intervalScript[i]
        func = str2func(_s.content)
        condition = {}
        if _s.hour: condition.update({"hours": int(_s.hour)})
        if _s.minute: condition.update({"minutes": int(_s.minute)})
        if _s.second: condition.update({"seconds": int(_s.second)})
        scheduler.add_job(
            func=funcList[_s.args], 
            trigger="interval",
            args=(_s.devices, _s.mqttClients, func) if _s.args == 1 else (_s.mqttClients, func),
            **condition
        )
    # 注入定时触发任务
    for i in advancedScript:
        _s = advancedScript[i]
        func = str2func(_s.content)
        condition = _s.model_dump(include={
            "year", "month", "day", "week", "day_of_week", 
            "hour", "minute", "second", "start_date", "end_date"
        }, exclude_defaults=True)
        scheduler.add_job(
            func=funcList[_s.args], 
            trigger="cron",
            args=(_s.devices, _s.mqttClients, func) if _s.args == 1 else (_s.mqttClients, func),
            **condition
        )
    logger.info(f"设备模型加载完毕...（耗时 {(time() - _start):.2f} s）")


def stopDeviceMonitor():
    logger.info("关闭设备数据采集线程...")
    removeDeviceModel()


def startup(app: FastAPI) -> Callable:
    async def app_start() -> None:
        initDeviceMonitor()
        initClient()
        scheduler.start()
        startWatchDog(scheduler)
        logger.info(f"system startup...")
    return app_start


def shutdown(app: FastAPI) -> Callable:
    async def app_stop() -> None:
        scheduler.remove_all_jobs()
        stopDeviceMonitor()
        logger.info(f"system shutdown...")
    return app_stop
